package com.basker.pisces.core.query.builder;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IField;
import com.basker.pisces.core.meta.bean.BeanMetaManager;
import com.basker.pisces.core.query.builder.CriterionBuilder.Operator;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.utils.ReflectionUtils;

public class CriterionBuilderHelper {

    public static List<CriterionBuilder> create(Class<?> queryType, Object example) {
        Assert.notNull(queryType, "parameter 'queryType' is required");

        return create(BeanMetaManager.getBeanMeta(queryType), example);
    }

    public static List<CriterionBuilder> create(IDataObjectMeta queryObjectMeta, Object example) {
        Assert.notNull(queryObjectMeta, "parameter 'queryObjectMeta' is required");
        Assert.notNull(example, "parameter 'example' is required");

        List<CriterionBuilder> criteria = Collections.emptyList();
        Map<String, Object> criteriaMap;

        Class<?> exampleType = example.getClass();
        if (exampleType.equals(queryObjectMeta.getDataObjectClass())) {
            criteriaMap = getCriteriaMapWithObjectMeta(queryObjectMeta, example);
        } else {
            criteriaMap = getCriteriaMapWithPropertyDescriptor(queryObjectMeta, example);
        }

        if (!CollectionUtils.isEmpty(criteriaMap)) {
            criteria = new ArrayList<>(criteriaMap.size());

            for (Entry<String, Object> entry : criteriaMap.entrySet()) {
                criteria.add(CriterionBuilder.create(entry.getKey(), Operator.Equal, entry.getValue()));
            }
        }

        return criteria;
    }

    private static Map<String, Object> getCriteriaMapWithObjectMeta(IDataObjectMeta queryObjectMeta, Object example) {
        Collection<IDataField> dataFields = queryObjectMeta.getDataFields();
        Map<String, Object> map = new LinkedHashMap<>(dataFields.size(), 1);

        for (IDataField field : dataFields) {
            Object value = field.getValue(example);
            if (value == null) {
                continue;
            }

            map.put(field.getName(), value);
        }

        return map;
    }

    private static Map<String, Object> getCriteriaMapWithPropertyDescriptor(IDataObjectMeta queryObjectMeta,
                                                                            Object example) {
        Class<?> exampleType = example.getClass();
        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(exampleType);

        Map<String, Object> map = new LinkedHashMap<>(propertyDescriptors.length, 1);

        boolean propertyMath = false;

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            if (propertyDescriptor.getReadMethod() == null || propertyDescriptor.getWriteMethod() == null) {
                continue;
            }

            if (propertyDescriptor.getReadMethod().getDeclaringClass().equals(Object.class)) {
                continue;
            }

            String fieldName = StringUtils.capitalize(propertyDescriptor.getName());
            IField field = queryObjectMeta.getField(fieldName);

            if (field instanceof IDataField) {
                propertyMath = true;

                Object value = ReflectionUtils.invokeMethod(propertyDescriptor.getReadMethod(), example);
                if (value != null) {
                    map.put(field.getName(), value);
                }
            }
        }

        if (!propertyMath) {
            throw ExceptionFactory.createRuntimeException(
                    "pisces-core.CriterionBuilderHelper.getCriteriaMapWithPropertyDescriptor-001",
                    "class '" + exampleType + "' is not match with '" + queryObjectMeta.getName() + ".");
        }

        return map;
    }

}
